/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Implementation for handy debug printing facility
 * \author Antti Maula <antti.maula@tkk.fi>
 *
 * Hashing for owndebugGetModuleBitByName() is done with code from:
 * http://www.cs.hmc.edu/~geoff/classes/hmc.cs070.200101/homework10/hashfuncs.html (2008.03.12)
 */
//#define GNU_SOURCE
#include "owndebug.h"
#include "ownutils.h"
#include <time.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>

//*****************************************************************************
#include <stdarg.h>
static long int initialized_timestamp_s;
static long int initialized_timestamp_us;
static int isinitialized = 0;
static FILE *print_file = NULL;
static FILE *log_file = NULL;
static int debugModeFlags = ( DEBUG_MODE_TIMESTAMP_HH_MM_SS_MS |
                              DEBUG_MODE_FORMAT_FILE_FUNC_LINE );
static unsigned int modulebits[OWNDEBUG_MAXMOD] = { 0xFFFFFFFF }; // Per default first 32 :)
unsigned int owndebug_next_free_modulenumber = 1;
//*****************************************************************************
//*****************************************************************************
static int print_level = 1;
static int log_level = 1;
//*****************************************************************************
//*****************************************************************************
#if 0

// Examples
textcolor(command, BRIGHT, WHITE, RED);
textcolor(command, BRIGHT, RED, BLACK);
textcolor(command, BRIGHT, YELLOW, BLACK);

#endif
//*****************************************************************************

unsigned int owndebugGetModuleBitByName(const char *modulename)
{
  // FIXME: This is just a  HASH:in method, so collisions may occur!
  unsigned int i,final;
  unsigned int h = 0, highorder;
  const int len = strlen(modulename);

  // Calculate hash to FIRST '.' found. (Should eliminate the extension from the name)
  for(i=0; i < len && modulename[i] != '.'; ++i) {
    highorder = h & 0xf8000000;    // extract high-order 5 bits from h
    h = h << 5;                    // shift h left by 5 bits
    h = h ^ (highorder >> 27);     // move the highorder 5 bits to the low-order
    h = h ^ (unsigned char)modulename[i];         // XOR h and ki
  }
  final = h % (OWNDEBUG_MAXMOD*32);
  //  printf("GetModuleBitByName (HASH):Input: '%s' - Value: %u, Final: %u\n", modulename, h, final);
  return final;
}
//*****************************************************************************

int debugInit()
{
  int i;

  // Initialize the module bits (all ON)
  for(i=0; i < OWNDEBUG_MAXMOD; ++i) {
    modulebits[i] = 0xFFFFFFFF;
  }

  // Initialize initialization time :)
  ownTime_GetTimeOfDay(&initialized_timestamp_s,
                       &initialized_timestamp_us);
  
  // Other inits.
  print_file = stderr;
  isinitialized = 1;
  atexit(debugDeinit);

  return OWNDEBUG_OK;
}
//*****************************************************************************

void debugDeinit()
{
  if (log_file != NULL) {
    fclose(log_file);
    log_file = NULL;
  }
  isinitialized = 0;
}
//*****************************************************************************

void debugAddMode(int mode)
{
  debugModeFlags |= mode;
}
//*****************************************************************************

void debugRemoveMode(int mode)
{
  debugModeFlags &= ~mode;
}
//*****************************************************************************

void debugSetMode(int mode)
{
  debugModeFlags = mode;
}
//*****************************************************************************

void debugSetModuleBit(int modulebitn, int value)
{
  int bptr;

  if (modulebitn < 0) {
    const unsigned int mask = value?0xFFFFFFFF:0x0;
    int i;
    for(i=0; i < OWNDEBUG_MAXMOD; ++i) {
      modulebits[i] = mask;
    }
  } else {
    bptr = modulebitn >> 5;
    if (value) {
      modulebits[bptr] |= 1<<(0x1F & modulebitn);
    } else {
      modulebits[bptr] &= ~(1<<(0x1F & modulebitn));
    }
  }
}
//*****************************************************************************

int debugSetLogFilename(const char *logfilename)
{
  int result = OWNDEBUG_ERROR;
  FILE *f; 
  
  if (logfilename != NULL) {
    f = fopen(logfilename, "wb");
    if (f != NULL) {
      log_file = f;
      result = OWNDEBUG_OK;
    }

  } else if (log_file != NULL) {
    fclose(log_file);
    log_file = NULL;
    result = OWNDEBUG_OK;

  }

  return result;
}
//*****************************************************************************

void debugSetPrintFILE(FILE *fname)
{
  log_file = fname;
}
//*****************************************************************************

void debugSetLogFILE(FILE *fname)
{
  print_file = fname;
}
//*****************************************************************************

void debugSetGlobalDebugLvl(int lvl) 
{
  // Global Sets both, print & logfile to same value
  assert(lvl >= 0);
  print_level = lvl;
  log_level = lvl;
}
//*****************************************************************************

void debugSetPrintLevel(int lvl)
{
  assert(lvl >= 0);
  print_level = lvl;
}
//*****************************************************************************

void debugSetLogLevel(int lvl)
{
  assert(lvl >= 0);
  log_level = lvl;
}
//*****************************************************************************

static inline int canModulePrint(const int modulebitn)
{
  assert( modulebitn <= (OWNDEBUG_MAXMOD*32) );

  

  //printf("Requested canModulePrint for %u\n", modulebitn);

  const int wordptr = modulebitn >> 5;
  //fprintf(stderr,"%x >> 5 = %x\n", modulebitn, wordptr);

  const int m = 1 << (0x1F & modulebitn);
  //fprintf(stderr,"(0x1F & modulebitn) = %x\n", m);

  const int r = modulebits[wordptr] & m;
  //fprintf(stderr,"%x & %x = %x\n", modulebits[wordptr], m, r);

  return r;

}
//*****************************************************************************

void debugEnableModulePrint(const char *module)
{
  if (module != NULL) {
    const unsigned int bit = owndebugGetModuleBitByName(module);
    debugSetModuleBit(bit, 1);
    fprintf(print_file,"OwnDebug: Enabled printing for module: '%s' (Got BitNumber: %u)\n", 
            module, bit);
  } else {
    fprintf(print_file,"OwnDebug: Enabled printing for ALL modules.\n");
    debugSetModuleBit(-1, 1);
  }
}
//*****************************************************************************

void debugDisableModulePrint(const char *module)
{
  if (module != NULL) {
    const unsigned int bit = owndebugGetModuleBitByName(module);
    debugSetModuleBit(bit, 0);
    fprintf(print_file,"OwnDebug: Disabled printing for module: '%s' (BitNumber: %u)\n", 
            module, bit);
  } else {
    fprintf(print_file,"OwnDebug: Disabled printing for ALL modules.\n");
    debugSetModuleBit(-1, 0);
  }
}
//*****************************************************************************

int debugGetPrintLevel()
{
  return print_level;
}
//*****************************************************************************

int debugGetLogLevel()
{
  return log_level;
}
//*****************************************************************************

FILE *debugGetPrintStream(void)
{
  return print_file;
}
//*****************************************************************************

FILE *debugGetLogStream(void)
{
  return log_file;
}
//*****************************************************************************

void debugPrint(int modulebitn,
                int lvl,
                int err,
                unsigned char color_attr,
                unsigned char color_fg, 
                unsigned char color_bg,
                const char *file, 
                const char *func, 
                int line, 
                const char *msg, 
                ...)
{
  va_list va; //parameter list
  struct timeval tv;
  long int *s = &tv.tv_sec, *us = &tv.tv_usec;
  struct tm *timeNow;
  char buffer[4096]; // Big big buffer (FIXME: Writes to this buffer are not limited!)
  char *buffer_ptr = buffer;

  // Does either level need printing?
  if (lvl > print_level && 
      lvl > log_level) 
    return; // Do nada when level is too big
  
  // Can we print?
  if (!canModulePrint(modulebitn)) 
    return; // Do nada, cannot print
  
  // Do we have ANY outputs available?
  if (log_file == NULL && print_file == NULL) 
    return; // No output, do nothing!
  
  // Trivial checks done, proceed.
  
  // Check coloring & apply if required.
  if (color_attr) {
    // Coloring requested!
    buffer_ptr += sprintf(buffer_ptr, "%c[%d;%d;%dm", 
                          0x1B, color_attr, color_fg + 30, color_bg + 40);
  }

  // Get the current time of day in S and US
  ownTime_GetTimeOfDay(s, us);

  // Print level
  buffer_ptr += sprintf(buffer_ptr, "[%.2d]", lvl);

  // Print threadID
#ifdef LINUX_OS
  // FIXME: Windows Compability not yet (ever?) implemented
  if (debugModeFlags & DEBUG_MODE_FORMAT_PRINTTHREADID) {
    buffer_ptr += sprintf(buffer_ptr, " thread:%ld,", pthread_self());
  }
#endif  

  // Print timestamp
  if (debugModeFlags & DEBUG_MODE_TIMESTAMP_HH_MM_SS_MS) {
    timeNow = localtime(&tv.tv_sec);
    buffer_ptr += sprintf(buffer_ptr, " %02d:%02d:%02d.%.3ld",
			  timeNow->tm_hour, 
			  timeNow->tm_min, 
			  timeNow->tm_sec, 
			  tv.tv_usec / 1000);
    
  } else if (debugModeFlags & DEBUG_MODE_TIMESTAMP_HH_MM_SS) {
    timeNow = localtime(&tv.tv_sec);
    buffer_ptr += sprintf(buffer_ptr, " %02d:%02d:%02d",
			  timeNow->tm_hour, 
			  timeNow->tm_min, 
			  timeNow->tm_sec);
    
  } else if (debugModeFlags & DEBUG_MODE_TIMESTAMP_DELTA_SS_US) {
    long int delta_s = *s - initialized_timestamp_s;
    long int delta_us = *us - initialized_timestamp_us;
    if (delta_us < 0) {
      delta_us += 1000000L;
      delta_s -= 1;
    }
    
    buffer_ptr += sprintf(buffer_ptr, " %ld.%06ld",
                          delta_s, delta_us );
  }
  
  // Print "location info"
  if (debugModeFlags & DEBUG_MODE_FORMAT_FILE_FUNC_LINE) {
    // Print all fields
    buffer_ptr += sprintf(buffer_ptr, ", %s:%s():%d; ", file, func, line);

  } else if (debugModeFlags & DEBUG_MODE_FORMAT_FILE_FUNC) {
    // Print only file&func
    buffer_ptr += sprintf(buffer_ptr, ", %s:%s(); ", file, func);

  } else if (debugModeFlags & DEBUG_MODE_FORMAT_FILE_LINE) {
    // Print only file&line
    buffer_ptr += sprintf(buffer_ptr, ", %s:%d; ", file, line);

  } else {
    // Print no header.
    buffer_ptr += sprintf(buffer_ptr, "; ");
  }
  
  // Print linefeed & tabular
  if (debugModeFlags & DEBUG_MODE_FORMAT_MSG_ON_NEXT_LINE)
    buffer_ptr += sprintf(buffer_ptr, "\r\n\t");

  // Print data
  va_start(va, msg); // get va list
  if (debugModeFlags & DEBUG_MODE_FORMAT_AALTONAATTORI) {
    char tmpstr[4096];
    int origprinted = vsprintf(tmpstr, msg, va);
    const char aaltochars[9][25] = { "\x1b[1;31;40m?\x1b[0;37;40m",
                                     "\x1b[1;31;40m!\x1b[0;37;40m",
                                     "\x1b[1;31;40m\"\x1b[0;37;40m",
                                     "\x1b[1;33;40m?\x1b[0;37;40m",
                                     "\x1b[1;33;40m!\x1b[0;37;40m",
                                     "\x1b[1;33;40m\"\x1b[0;37;40m",
                                     "\x1b[1;34;40m?\x1b[0;37;40m",
                                     "\x1b[1;34;40m!\x1b[0;37;40m",
                                     "\x1b[1;34;40m\"\x1b[0;37;40m" };
    const int aaltochars_len = 21;

    int li;
    for(li = 0; li < origprinted; ++li) {
      const long int rvalue = random();
      if ((rvalue % 20) == 0 && isalpha(tmpstr[li])) {
        strcpy(buffer_ptr, aaltochars[random() % 9]);
        buffer_ptr += aaltochars_len;
        
      }
      *buffer_ptr = tmpstr[li];
      buffer_ptr += 1;
    }
  } else {
    buffer_ptr += vsprintf(buffer_ptr, msg, va);
    
  }
  va_end(va); // End va list

  // Print errno if not zero (Only if strerror_r is supported. The traditional
  // strerror() won't help, because we don't have synchronozation here.
#if defined( LINUX_OS )
  if (err != 0) {
    char errormsg[64] = "<Not available>";
    char *ptr = errormsg;
    strerror_r(err, errormsg, sizeof(errormsg));
    ptr = errormsg;
    buffer_ptr += sprintf(buffer_ptr," (%d: %s)", err, ptr);
  }
#endif 

  if (color_attr) {
    buffer_ptr += sprintf(buffer_ptr, "%c[%dm", 0x1B, OWNDEBUG_COLOR_ATTR_RESET);
  }
  
  // Terminate line
  buffer_ptr += sprintf(buffer_ptr, "\n"); 

  // Do trivial check, the operation may have segfaulted before this :)
  assert( (buffer_ptr - buffer) < (int)sizeof(buffer) );

  // Write out! (Writes are atomic, so no mutexes needed)
  if (print_file != NULL && lvl <= print_level) {
    fwrite(buffer, buffer_ptr-buffer, 1, print_file);

    // If IMMEDIATE FLUSH requested.
    if (debugModeFlags & DEBUG_MODE_IMMEDIATE_FLUSH) {
      fflush(print_file);
    }
  }
  
  if (log_file != NULL && lvl <= log_level) {
    fwrite(buffer, buffer_ptr-buffer, 1, log_file);
    
    // If IMMEDIATE FLUSH requested.
    if (debugModeFlags & DEBUG_MODE_IMMEDIATE_FLUSH) {
      fflush(log_file);
    }
  }
  
  // end
  return;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
